//
//  LYScanner.swift
//  RxSwift_Practice
//
//  Created by 李永彬 on 2020/4/28.
//  Copyright © 2020 李永彬. All rights reserved.
//

import Foundation
import CoreBluetooth

internal class LYScanner: LYCBCentralManagerDiscoveryDelegate {
    
    // MARK: Type Aliases

    internal typealias ScanCompletionHandler = ((_ result: [LYDiscovery]?, _ error: LYError?) -> Void)
    private var scanHandlers: (progressHandler: LYCentral.ScanProgressHandler?, completionHandler: ScanCompletionHandler )?
    private var discoveries = [LYDiscovery]()
    
    // MARK: Enums

    enum LYError: Error {
        case noCentralManagerSet
        case busy
        case interrupted
    }
    
    // MARK: Properties

    internal var configuration: LYConfiguration?
    internal var centralManager: CBCentralManager!
    private var busy = false
    private var durationTimer: Timer?

    internal func scanWithDuration(_ duration: TimeInterval, updateDuplicates: Bool, progressHandler: LYCentral.ScanProgressHandler? = nil, completionHandler: @escaping ScanCompletionHandler) throws {
        
        do {
            try validateForActivity()
            busy = true
            scanHandlers = ( progressHandler: progressHandler, completionHandler: completionHandler)
            let options = [CBCentralManagerScanOptionAllowDuplicatesKey: updateDuplicates]
            
            let haveDiscovery = self.getPhoneConnectedDevices(serviceUUIDs: ["6006", "7006", "180D"])
            scanHandlers?.progressHandler?(haveDiscovery)
            
            centralManager.scanForPeripherals(withServices: configuration?.servicesArray, options: options)
            if(duration > 0) {
                durationTimer = Timer.scheduledTimer(timeInterval: duration, target: self, selector: #selector(LYScanner.durationTimerElapsed), userInfo: nil, repeats: false)
            }
            
        } catch let error {
            throw error
        }
    }
    
    internal func interruptScan() {
        guard busy else {
            return
        }
        endScan(.interrupted)
    }
    
    // MARK: Private Functions

    private func validateForActivity() throws {
          
        guard !busy else {
            throw LYError.busy
        }
          
        guard centralManager != nil else {
            throw LYError.noCentralManagerSet
        }
    }
      
    @objc private func durationTimerElapsed() {
        endScan(nil)
    }
     
    private func endScan(_ error: LYError?) {
        invalidateTimer()
        centralManager.stopScan()
        let completionHandler = scanHandlers?.completionHandler
        let discoveries = self.discoveries
        scanHandlers = nil
        self.discoveries.removeAll()
        busy = false
        completionHandler?(discoveries, error)
    }
    
    private func invalidateTimer() {
        if let durationTimer = self.durationTimer {
            durationTimer.invalidate()
            self.durationTimer = nil
        }
    }
    
    func addDiscoveredDevice(_ device:LYDiscovery) {
        let result: Bool = device.localName != nil || device.localName!.isEqual("")
        guard result else {
            return
        }
        self.discoveries.append(device)
    }
    
    // MARK: LYCBCentralManagerDiscoveryDelegate
    
    internal func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        
        guard busy else {
            return
        }
        let RSSI = Int(truncating: RSSI)
        let remotePeripheral = LYRemotePeripheral(identifier: peripheral.identifier, peripheral: peripheral)
        remotePeripheral.configuration = configuration
        
        let discovery = LYDiscovery(advertisementData: advertisementData, remotePeripheral: remotePeripheral, RSSI: RSSI)
        
        if let index = discoveries.firstIndex(of: discovery) {
            discoveries[index] = discovery
        }
        else {
            self.addDiscoveredDevice(discovery)
        }
        scanHandlers?.progressHandler?([ discovery ])
    }
    
    func getPhoneConnectedDevices(serviceUUIDs: [String]) -> [LYDiscovery] {
        var devices = [LYDiscovery]()
        var arr = [CBUUID]()

        if serviceUUIDs.count > 0 {
            for uuid:String in serviceUUIDs {
                arr.append(CBUUID.init(string: uuid))
            }
        }
        
        let phoneConnectedArray = self.centralManager.retrieveConnectedPeripherals(withServices: arr)
        
        for (index, _) in phoneConnectedArray.enumerated() {
            let p: CBPeripheral = phoneConnectedArray[index]
            let remotePeripheral = LYRemotePeripheral(identifier: p.identifier, peripheral: p)
            remotePeripheral.configuration = configuration
            let discovery = LYDiscovery(advertisementData: nil, remotePeripheral: remotePeripheral, RSSI: nil)
            devices.append(discovery)
        }
        return devices
    }
}
